package geom;

/**
 * @author Robbe
 * Homogeneous transformations
 */
public class Transform {
	
	protected double t11, t12, t13, t14, t21, t22, t23, t24, t31, t32, t33, t34, t41, t42, t43, t44;
	
	/********************************************************************************
	 * constructors
	 ********************************************************************************/	
	
	/**
	 * creates an identity transformation
	 */
	public Transform() {
		set(
				1,0,0,0,
				0,1,0,0,
				0,0,1,0,
				0,0,0,1
		);	
	}
	
	/**
	 * creates an arbitrary transformation
	 * 
	 * @param t11
	 * @param t12
	 * @param t13
	 * @param t14
	 * @param t21
	 * @param t22
	 * @param t23
	 * @param t24
	 * @param t31
	 * @param t32
	 * @param t33
	 * @param t34
	 * @param t41
	 * @param t42
	 * @param t43
	 * @param t44
	 */
	public Transform(double t11, double t12, double t13, double t14, double t21, double t22, double t23, double t24, double t31, double t32, double t33, double t34, double t41, double t42, double t43, double t44) {
		set(
				t11, t12, t13, t14, 
				t21, t22, t23, t24, 
				t31, t32, t33, t34, 
				t41, t42, t43, t44 				
		);
	}
	
	/********************************************************************************
	 * factory functions
	 ********************************************************************************/	
	
	public static Transform createIdentity() {
		return new Transform();
	}
	
	public static Transform createTranslation(double tx, double ty, double tz) {
		return new Transform(
				1,0,0,tx,
				0,1,0,ty,
				0,0,1,tz,
				0,0,0,1
		);
	}
	
	public static Transform createRotation(double theta, double x, double y, double z) {
		double norm = Math.sqrt(x*x + y*y + z*z);
		x /= norm;
		y /= norm;
		z /= norm;		
	
		double s = Math.sin(theta);
		double c = Math.cos(theta);
		double t = 1-c;
		
		return new Transform(
				t*x*x+c,	t*x*y-s*z,	t*x*z+s*y,	0,
				t*x*y+s*z,	t*y*y+c,	t*y*z-s*x,	0,
				t*x*z-s*y,	t*y*z+s*x,	t*z*z+c,	0,
				0,	0,	0,	1				
		);
	}
	
	public static Transform createScale(double sx, double sy, double sz) {
		return new Transform(
				sx,0,0,0,
				0,sy,0,0,
				0,0,sz,0,
				0,0,0,1
		);
	}
	
	public static Transform createShear(double s1, double s2, double s3, double s4, double s5, double s6) {
		return new Transform(
				1,	s2,	s5,	0,
				s1,	1,	s6,	0,
				s3,	s4,	1,	0,
				0,	0,	0,	1
		);
	}
	
	/********************************************************************************
	 * calculations
	 ********************************************************************************/
	
	/**
	 * concatenates this transform with another
	 * 
	 * @param t
	 * @return concatenation (new instance)
	 */
	public Transform concatenation(Transform t) {		
 		return new Transform(
 				t.t11*t11 + t.t12*t21 + t.t13*t31 + t.t14*t41, t.t11*t12 + t.t12*t22 + t.t13*t32 + t.t14*t42, t.t11*t13 + t.t12*t23 + t.t13*t33 + t.t14*t43, t.t11*t14 + t.t12*t24 + t.t13*t34 + t.t14*t44, 
 				t.t21*t11 + t.t22*t21 + t.t23*t31 + t.t24*t41, t.t21*t12 + t.t22*t22 + t.t23*t32 + t.t24*t42, t.t21*t13 + t.t22*t23 + t.t23*t33 + t.t24*t43, t.t21*t14 + t.t22*t24 + t.t23*t34 + t.t24*t44, 
 				t.t31*t11 + t.t32*t21 + t.t33*t31 + t.t34*t41, t.t31*t12 + t.t32*t22 + t.t33*t32 + t.t34*t42, t.t31*t13 + t.t32*t23 + t.t33*t33 + t.t34*t43, t.t31*t14 + t.t32*t24 + t.t33*t34 + t.t34*t44, 
 				t.t41*t11 + t.t42*t21 + t.t43*t31 + t.t44*t41, t.t41*t12 + t.t42*t22 + t.t43*t32 + t.t44*t42, t.t41*t13 + t.t42*t23 + t.t43*t33 + t.t44*t43, t.t41*t14 + t.t42*t24 + t.t43*t34 + t.t44*t44
 		);	
 	}		
	
	/**
	 * transforms an array of homogeneous points or vectors.
	 * 
	 * @param points
	 * @return
	 */
	public double[] homogeneousPointsTransformed(double[] points) {
		assert(points.length % 4 == 0); // input data must be homogeneous points
		double[] transformed = new double[points.length];
		double x,y,z,w;                   
		for (int i = 0; i < transformed.length / 4; i++) {
			// extract input coordinates for point
			x = points[i*4 + 0];
			y = points[i*4 + 1];
			z = points[i*4 + 2];
			w = points[i*4 + 3];
			// store output coordinates for point
			transformed[i*4 + 0] = t11*x + t12*y + t13*z + t14*w; // x
			transformed[i*4 + 1] = t21*x + t22*y + t23*z + t24*w; // y
			transformed[i*4 + 2] = t31*x + t32*y + t33*z + t34*w; // z
			transformed[i*4 + 3] = t41*x + t42*y + t43*z + t44*w; // w			
		}
		return transformed;
	}
	
	/********************************************************************************
	 * miscellaneous functions
	 ********************************************************************************/
	
	public void set(double t11, double t12, double t13, double t14, double t21, double t22, double t23, double t24, double t31, double t32, double t33, double t34, double t41, double t42, double t43, double t44) {
		this.t11 = t11;	this.t12 = t12;	this.t13 = t13;	this.t14 = t14;
		this.t21 = t21;	this.t22 = t22;	this.t23 = t23;	this.t24 = t24;
		this.t31 = t31;	this.t32 = t32;	this.t33 = t33;	this.t34 = t34;
		this.t41 = t41;	this.t42 = t42;	this.t43 = t43;	this.t44 = t44;		
	}		
	
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(t11).append("\t").append(t12).append("\t").append(t13).append("\t").append(t14).append("\t").append("\n")
		.append(t21).append("\t").append(t22).append("\t").append(t23).append("\t").append(t24).append("\t").append("\n")
		.append(t31).append("\t").append(t32).append("\t").append(t33).append("\t").append(t34).append("\t").append("\n")
		.append(t41).append("\t").append(t42).append("\t").append(t43).append("\t").append(t44).append("\t").append("\n");
		return sb.toString();
	}

}
